package xyz.apiote.bimba.czwek.repo

import android.content.Context
import android.text.format.DateUtils
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.api.AlertCauseV1
import xyz.apiote.bimba.czwek.api.AlertEffectV1
import xyz.apiote.bimba.czwek.api.AlertV1
import xyz.apiote.bimba.czwek.api.DepartureV1
import xyz.apiote.bimba.czwek.api.DepartureV2
import xyz.apiote.bimba.czwek.api.Time
import xyz.apiote.bimba.czwek.api.UnknownResourceVersionException
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit

enum class AlertCause {
	UNKNOWN, OTHER, TECHNICAL_PROBLEM, STRIKE, DEMONSTRATION, ACCIDENT, HOLIDAY, WEATHER, MAINTENANCE,
	CONSTRUCTION, POLICE_ACTIVITY, MEDICAL_EMERGENCY;

	companion object {
		fun of(type: AlertCauseV1): AlertCause {
			return when (type) {
				AlertCauseV1.UNKNOWN -> valueOf("UNKNOWN")
				AlertCauseV1.OTHER -> valueOf("OTHER")
				AlertCauseV1.TECHNICAL_PROBLEM -> valueOf("TECHNICAL_PROBLEM")
				AlertCauseV1.STRIKE -> valueOf("STRIKE")
				AlertCauseV1.DEMONSTRATION -> valueOf("DEMONSTRATION")
				AlertCauseV1.ACCIDENT -> valueOf("ACCIDENT")
				AlertCauseV1.HOLIDAY -> valueOf("HOLIDAY")
				AlertCauseV1.WEATHER -> valueOf("WEATHER")
				AlertCauseV1.MAINTENANCE -> valueOf("MAINTENANCE")
				AlertCauseV1.CONSTRUCTION -> valueOf("CONSTRUCTION")
				AlertCauseV1.POLICE_ACTIVITY -> valueOf("POLICE_ACTIVITY")
				AlertCauseV1.MEDICAL_EMERGENCY -> valueOf("MEDICAL_EMERGENCY")
			}
		}
	}
}

enum class AlertEffect {
	UNKNOWN, OTHER, NO_SERVICE, REDUCED_SERVICE, SIGNIFICANT_DELAYS, DETOUR, ADDITIONAL_SERVICE,
	MODIFIED_SERVICE, STOP_MOVED, NONE, ACCESSIBILITY_ISSUE;

	companion object {
		fun of(type: AlertEffectV1): AlertEffect {
			return when (type) {
				AlertEffectV1.UNKNOWN -> valueOf("UNKNOWN")
				AlertEffectV1.OTHER -> valueOf("OTHER")
				AlertEffectV1.NO_SERVICE -> valueOf("NO_SERVICE")
				AlertEffectV1.REDUCED_SERVICE -> valueOf("REDUCED_SERVICE")
				AlertEffectV1.SIGNIFICANT_DELAYS -> valueOf("SIGNIFICANT_DELAYS")
				AlertEffectV1.DETOUR -> valueOf("DETOUR")
				AlertEffectV1.ADDITIONAL_SERVICE -> valueOf("ADDITIONAL_SERVICE")
				AlertEffectV1.MODIFIED_SERVICE -> valueOf("MODIFIED_SERVICE")
				AlertEffectV1.STOP_MOVED  -> valueOf("STOP_MOVED")
				AlertEffectV1.NONE -> valueOf("NONE")
				AlertEffectV1.ACCESSIBILITY_ISSUE -> valueOf("ACCESSIBILITY_ISSUE")
			}
		}
	}
}

data class Alert(
	val header: String,
	val description: String,
	val url: String,
	val cause: AlertCause,
	val effect: AlertEffect
) {
	constructor(a: AlertV1) : this(a.header, a.Description, a.Url, AlertCause.of(a.Cause), AlertEffect.of(a.Effect))
}

data class StopDepartures(
	val departures: List<Departure>,
	val stop: Stop,
	val alerts: List<Alert>
)

data class Departure(
	val ID: String,
	val time: Time,
	val status: ULong,
	val isRealtime: Boolean,
	val vehicle: Vehicle,
	val boarding: UByte
) {

	constructor(d: DepartureV1) : this(
		d.ID,
		d.time,
		d.status,
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding
	)

	constructor(d: DepartureV2) : this(
		d.ID,
		d.time,
		d.status,
		d.isRealtime,
		Vehicle(d.vehicle),
		d.boarding
	)

	fun statusText(context: Context?): String {
		val now = Instant.now().atZone(ZoneId.systemDefault())
		val departureTime = ZonedDateTime.of(
			now.year, now.monthValue, now.dayOfMonth,
			time.Hour.toInt(), time.Minute.toInt(), time.Second.toInt(), 0, ZoneId.of(time.Zone)
		).plus(time.DayOffset.toLong(), ChronoUnit.DAYS)
		return when (val r = status.toUInt()) {
			0u -> DateUtils.getRelativeTimeSpanString(
				departureTime.toEpochSecond() * 1000,
				now.toEpochSecond() * 1000,
				DateUtils.MINUTE_IN_MILLIS,
				DateUtils.FORMAT_ABBREV_RELATIVE
			).toString()

			1u -> context?.getString(R.string.departure_momentarily) ?: "momentarily"
			2u -> context?.getString(R.string.departure_now) ?: "now"
			3u -> context?.getString(R.string.departure_departed) ?: "departed"
			else -> throw UnknownResourceVersionException("VehicleStatus/$r", 1u)
		}
	}

	fun timeString(context: Context): String {
		return if (isRealtime) {
			context.getString(
				R.string.at_time_realtime, time.Hour.toInt(), time.Minute.toInt(), time.Second.toInt()
			)
		} else {
			context.getString(R.string.at_time, time.Hour.toInt(), time.Minute.toInt())
		}
	}

	fun boardingText(context: Context): String {
		// todo [3.x] probably should take into account (on|off)-boarding only, on demand
		return when {
			boarding == (0b0000_0000).toUByte() -> context.getString(R.string.no_boarding)
			boarding.and(0b0011_0011u) == (0b0000_0001).toUByte() -> context.getString(R.string.on_boarding)
			boarding.and(0b0011_0011u) == (0b0001_0000).toUByte() -> context.getString(R.string.off_boarding)
			boarding.and(0b0011_0011u) == (0b0001_0001).toUByte() -> context.getString(R.string.boarding)
			else -> context.getString(R.string.on_demand)
		}
	}
}